package event;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.HashMap;
import database.EventSerializer;
import event.DEFINES.CEPLEventDataFieldValue;
import event.DEFINES.CEPLEventDataFieldType;
import event.EventFormatManager;

public class Event implements Comparable<Timestamp> {
	private static long eventID = EventSerializer.instance()
			.getMaximumSerializedID();

	// used as an internal data container -> easy for storing in a database
	private static final class CEPLSerializableEvent implements Serializable {
		private static final long serialVersionUID = -8592176758384222981L;

		final long ID; // ID
		// ID of the event generator, could be a query -> set internally
		final long streamID;
		final Timestamp timestamp; // systemtime when creating this event
		final boolean isForwarded;
		final boolean isPrimitive;
		final boolean isCacheable;
		final byte[] data;

		public CEPLSerializableEvent(long _ID, long _streamID,
				Timestamp _timestamp, boolean _forwarded, boolean _primitive,
				boolean _cacheable, byte[] _data) {
			ID = _ID;
			streamID = _streamID;
			timestamp = _timestamp;
			isCacheable = _cacheable;
			isForwarded = _forwarded;
			isPrimitive = _primitive;
			data = _data;
		}

		public CEPLSerializableEvent(long _ID, long _streamID,
				Timestamp _timestamp, boolean _forwarded, boolean _primitive,
				boolean _cacheable) {
			this(_ID, _streamID, _timestamp, _forwarded, _primitive,
					_cacheable, null);
		}
	}

	private Event.CEPLSerializableEvent serializableData = null;
	// fieldname -> fieldValue
	private HashMap<String, CEPLEventDataFieldValue> fieldValues = null;

	// for buffer management
	private boolean removeFromBuffer = false;
	private int bufferIndex = -1;

	// constructor, using the given timestamp
	public Event(long _streamID, boolean _forwarded, boolean _primitive,
			boolean _cacheable, byte[] eventData, java.sql.Timestamp timestamp)
			throws BufferOverflowException, BufferUnderflowException {
		try {
			serializableData = new Event.CEPLSerializableEvent(++eventID,
					_streamID, timestamp, _forwarded, _primitive, _cacheable,
					eventData);
			EventFormatManager fm = EventFormatManager.instance();
			fieldValues = fm.parseByteArray(serializableData.streamID,
					serializableData.data);

		} catch (BufferOverflowException e) {
			throw e;
		} catch (BufferUnderflowException e) {
			throw e;
		}
	}

	// constructor, using the current timestamp
	public Event(long _streamID, boolean _forwarded, boolean _primitive,
			boolean _cacheable, byte[] eventData)
			throws BufferOverflowException, BufferUnderflowException {
		try {
			Calendar calendar = Calendar.getInstance();
			java.util.Date now = calendar.getTime();
			java.sql.Timestamp currentTimestamp = new java.sql.Timestamp(
					now.getTime());

			serializableData = new Event.CEPLSerializableEvent(++eventID,
					_streamID, currentTimestamp, _forwarded, _primitive,
					_cacheable, eventData);
			EventFormatManager fm = EventFormatManager.instance();
			fieldValues = fm.parseByteArray(serializableData.streamID,
					serializableData.data);
		} catch (BufferOverflowException e) {
			throw e;
		} catch (BufferUnderflowException e) {
			throw e;
		}
	}

	// constructor, not using a byte array
	public Event(long _streamID, boolean _forwarded, boolean _primitive,
			boolean _cacheable,
			HashMap<String, CEPLEventDataFieldValue> _parsedData) {
		Calendar calendar = Calendar.getInstance();
		java.util.Date now = calendar.getTime();
		java.sql.Timestamp currentTimestamp = new java.sql.Timestamp(
				now.getTime());

		serializableData = new Event.CEPLSerializableEvent(++eventID,
				_streamID, currentTimestamp, _forwarded, _primitive, _cacheable);
		fieldValues = _parsedData;
	}

	// constructor, deserializing a stored event
	public Event(byte[] bytes) {
		deserializeFromByteArray(bytes);
	}

	protected void finalize() {
		if (fieldValues != null)
			fieldValues.clear();
	}

	public long getID() {
		return serializableData.ID;
	}

	public long getStreamID() {
		return serializableData.streamID;
	}

	public Timestamp getTimestamp() {
		return serializableData.timestamp;
	}

	public boolean isForwarded() {
		return serializableData.isForwarded;
	}

	public boolean isPrimitive() {
		return serializableData.isPrimitive;
	}

	public boolean isCacheable() {
		return serializableData.isCacheable;
	}

	public boolean removeFromBuffer() {
		return removeFromBuffer;
	}

	public void setRemoveFromBuffer(boolean bool) {
		removeFromBuffer = bool;
	}

	public int getBufferIndex() {
		return bufferIndex;
	}

	public void setBufferIndex(int i) {
		bufferIndex = i;
	}

	// == field values in a byte array
	public byte[] getEventData() {
		return serializableData.data;
	}

	public long getLong(String key) throws UnknownDataFieldException {
		CEPLEventDataFieldValue temp = fieldValues.get(key.toLowerCase());
		if (temp == null)
			throw new UnknownDataFieldException();

		Long i = (Long) (temp.value);
		return i.longValue();
	}

	public int getInteger(String key) throws UnknownDataFieldException {
		CEPLEventDataFieldValue temp = fieldValues.get(key.toLowerCase());
		if (temp == null)
			throw new UnknownDataFieldException();

		Integer i = (Integer) (temp.value);
		return i.intValue();
	}

	public short getShort(String key) throws UnknownDataFieldException {
		CEPLEventDataFieldValue temp = fieldValues.get(key.toLowerCase());
		if (temp == null)
			throw new UnknownDataFieldException();

		Short i = (Short) (temp.value);
		return i.shortValue();
	}

	public char getCharacter(String key) throws UnknownDataFieldException {
		CEPLEventDataFieldValue temp = fieldValues.get(key.toLowerCase());
		if (temp == null)
			throw new UnknownDataFieldException();

		Character i = (Character) (temp.value);
		return i.charValue();
	}

	public float getFloat(String key) throws UnknownDataFieldException {
		CEPLEventDataFieldValue temp = fieldValues.get(key.toLowerCase());
		if (temp == null)
			throw new UnknownDataFieldException();

		Float i = (Float) (temp.value);
		return i.floatValue();
	}

	public double getDouble(String key) throws UnknownDataFieldException {
		CEPLEventDataFieldValue temp = fieldValues.get(key.toLowerCase());
		if (temp == null)
			throw new UnknownDataFieldException();

		Double i = (Double) (temp.value);
		return i.doubleValue();
	}

	public String getString(String key) throws UnknownDataFieldException {
		CEPLEventDataFieldValue temp = fieldValues.get(key.toLowerCase());
		if (temp == null)
			throw new UnknownDataFieldException();

		String i = (String) (temp.value);
		return new String(i);
	}

	public Double getEncapsulatingDouble(String key) {
		CEPLEventDataFieldValue temp = fieldValues.get(key.toLowerCase());
		if (temp.type == CEPLEventDataFieldType.CHAR) {
			return new Double((Character) temp.value);
		} else if (temp.type == CEPLEventDataFieldType.SHORT) {
			return new Double((Short) temp.value);
		} else if (temp.type == CEPLEventDataFieldType.INT) {
			return new Double((Integer) temp.value);
		} else if (temp.type == CEPLEventDataFieldType.LONG) {
			return new Double((Long) temp.value);
		} else if (temp.type == CEPLEventDataFieldType.FLOAT) {
			return new Double((Float) temp.value);
		} else if (temp.type == CEPLEventDataFieldType.DOUBLE) {
			return new Double((Double) temp.value);
		}

		return null;
	}

	// == serialized internal CEPLStoredEvent object
	// not to be confused with getEventData()
	public byte[] toSerializedByteArray() {
		byte[] result = null;

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutput out = null;
		try {
			out = new ObjectOutputStream(baos);
			out.writeObject(serializableData);
			out.close();
			result = baos.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return result;
	}

	public void deserializeFromByteArray(byte[] bytes)
			throws BufferOverflowException, BufferUnderflowException {
		try {
			ObjectInputStream in = new ObjectInputStream(
					new ByteArrayInputStream(bytes));
			serializableData = (CEPLSerializableEvent) in.readObject();
			in.close();
			EventFormatManager fm = EventFormatManager.instance();
			fieldValues = fm.parseByteArray(serializableData.streamID,
					serializableData.data);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (BufferOverflowException e) {
			throw e;
		} catch (BufferUnderflowException e) {
			throw e;
		}
	}

	@Override
	public int compareTo(Timestamp o) {
		final int BEFORE = -1;
		final int EQUAL = 0;
		final int AFTER = 1;

		long oMS = o.getTime();
		long tMS = getTimestamp().getTime();

		if (tMS == oMS)
			return EQUAL;
		else if (tMS > oMS)
			return AFTER;
		else if (tMS < oMS)
			return BEFORE;
		else
			return EQUAL;
	}
}
